package fr.esipe.escape.game;

import java.awt.Color;
import java.awt.Graphics2D;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;

import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;

import fr.esipe.escape.Set;
import fr.esipe.escape.collisions.Collision;
import fr.esipe.escape.collisions.DestroyObserver;
import fr.esipe.escape.collisions.Mask;
import fr.esipe.escape.game.bonus.Bonus;
import fr.esipe.escape.game.spaceship.Player;
import fr.esipe.escape.game.spaceship.enemy.SpaceShipEnemy;
import fr.esipe.escape.game.weapon.Fireball;
import fr.esipe.escape.game.weapon.Laser;
import fr.esipe.escape.game.weapon.Missile;
import fr.esipe.escape.game.weapon.Munitions;
import fr.esipe.escape.game.weapon.Shiboleet;
import fr.esipe.escape.game.weapon.Weapon;

/**
 * This class implements the differents Maps (Worlds).
 * 
 * @author Celine Perillous <cperillous@etudiant.univ-mlv.fr>
 * @author Jeremy Lor <jlor@etudiant.univ-mlv.fr>
 * 
 */
public class Map {

	/**
	 * The unique world.
	 */
	private static World world;

	/**
	 * The Player.
	 */
	private static Player spaceshipPlayer = null;

	/**
	 * The spaceshipEnemyList.
	 */
	private static LinkedBlockingQueue<SpaceShipEnemy> spaceshipEnemyList = new LinkedBlockingQueue<SpaceShipEnemy>();

	/**
	 * The weapon List.
	 */
	private static LinkedBlockingQueue<Weapon> weaponsList = new LinkedBlockingQueue<Weapon>();

	/**
	 * The weaponsTable for munitions.
	 */
	private static ConcurrentHashMap<String, Munitions> weaponsTable = new ConcurrentHashMap<>(
			4);

	/**
	 * The bonus List.
	 */
	private static LinkedBlockingQueue<Bonus> bonusList = new LinkedBlockingQueue<Bonus>();

	/**
	 * The active weapon.
	 */
	private static int weaponActive = 0;

	/**
	 * The Highscore.
	 */
	private static int Highscore;

	/**
	 * The boolean win.
	 */
	private static boolean Win;

	/**
	 * The boolean lose.
	 */
	private static boolean Lose;

	/**
	 * Create a Map.
	 */
	public Map() {
		Vec2 vec = new Vec2(0, 0);
		world = new World(vec, true);
		world.setContinuousPhysics(true);
		world.setContactListener(new Collision());

		if (getSpaceshipEnemyList() != null)
			getSpaceshipEnemyList().clear();

		if (getWeaponsList() != null)
			getWeaponsList().clear();

		if (getWeaponsTable() != null) {
			getWeaponsTable().clear();
		}

		if (getBonusList() != null)
			getBonusList().clear();

		/**
		 * Map bounds.
		 */
		// TOP CORNER
		Body body;
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.STATIC;
		bodyDef.position.set(0, -50);
		PolygonShape blockShape = new PolygonShape();
		blockShape.setAsBox(Set.getSet().getWidth(), 15 / 2);
		FixtureDef fixtureDef = new FixtureDef();
		fixtureDef.shape = blockShape;
		fixtureDef.userData = EntityType.WALL;
		fixtureDef.filter.categoryBits = Mask.CATEGORY_WALL;
		fixtureDef.filter.maskBits = Mask.MASK_WALL;
		fixtureDef.density = 1.f;
		fixtureDef.friction = 1.f;
		fixtureDef.restitution = .1f;

		body = world.createBody(bodyDef);
		body.createFixture(fixtureDef);

		// BOTTOM CORNER
		bodyDef = new BodyDef();
		bodyDef.type = BodyType.STATIC;
		bodyDef.position.set(0, Set.getSet().getHeight() + 100);
		bodyDef.userData = this;
		blockShape = new PolygonShape();
		blockShape.setAsBox(Set.getSet().getWidth(), 0);
		fixtureDef = new FixtureDef();
		fixtureDef.shape = blockShape;
		fixtureDef.userData = EntityType.WALL;
		fixtureDef.filter.categoryBits = Mask.CATEGORY_WALL;
		fixtureDef.filter.maskBits = Mask.MASK_WALL;
		fixtureDef.density = 1.f;
		fixtureDef.friction = 1.f;
		fixtureDef.restitution = .1f;
		body = world.createBody(bodyDef);
		body.createFixture(fixtureDef);

		// LEFT CORNER
		bodyDef = new BodyDef();
		bodyDef.type = BodyType.STATIC;
		bodyDef.position.set(-50, 0);
		bodyDef.userData = this;
		blockShape = new PolygonShape();
		blockShape.setAsBox(0, Set.getSet().getHeight());
		fixtureDef = new FixtureDef();
		fixtureDef.shape = blockShape;
		fixtureDef.userData = EntityType.WALL;
		fixtureDef.filter.categoryBits = Mask.CATEGORY_WALL;
		fixtureDef.filter.maskBits = Mask.MASK_WALL;
		fixtureDef.density = 1.f;
		fixtureDef.friction = 1.f;
		fixtureDef.restitution = .1f;
		body = world.createBody(bodyDef);
		body.createFixture(fixtureDef);

		// RIGHT CORNER
		bodyDef = new BodyDef();
		bodyDef.type = BodyType.STATIC;
		bodyDef.position.set(Set.getSet().getWidth() + 50, 0);
		bodyDef.userData = this;
		blockShape = new PolygonShape();
		blockShape.setAsBox(0, Set.getSet().getHeight());
		fixtureDef = new FixtureDef();
		fixtureDef.shape = blockShape;
		fixtureDef.userData = EntityType.WALL;
		fixtureDef.filter.categoryBits = Mask.CATEGORY_WALL;
		fixtureDef.filter.maskBits = Mask.MASK_WALL;
		fixtureDef.density = 1.f;
		fixtureDef.friction = 1.f;
		fixtureDef.restitution = .1f;
		body = world.createBody(bodyDef);
		body.createFixture(fixtureDef);

		/**
		 * Ammos for weapons.
		 */
		weaponsTable.put("Missile", new Munitions("Missile", 100));
		weaponsTable.put("Fireball", new Munitions("Fireball", 10));
		weaponsTable.put("Shiboleet", new Munitions("Shiboleet", 3));
		weaponsTable.put("Laser", new Munitions("Laser", 1));
	}

	/**
	 * Display all elements.
	 * 
	 * @param graphics
	 */
	public void render(Graphics2D graphics) {
		graphics.setColor(Color.BLACK);
	}

	/**
	 * Update the Map (world).
	 */
	public void updateMap() {
		world.step(1 / 30f, 15, 8);
		DestroyObserver.update();
	}

	/**
	 * Shoot a weapon from player.
	 * 
	 * @param weaponActive
	 *            the active weapon.
	 * @param direction
	 *            The direction.
	 * @param sp
	 *            The Player.
	 */
	public static void shootWeaponFromPlayer(int weaponActive, Vec2 direction,
			Player sp) {
		switch (weaponActive) {
		case 0:
			if (!Map.getWeaponsTable().get("Missile").isEmpty()) {
				final Weapon missile = new Missile(new Vec2(sp.getBody()
						.getPosition().x, sp.getBody().getPosition().y),
						direction, true);
				Map.addWeapon(missile);
				Map.remove("Missile");
			}
			break;
		case 1:
			if (!Map.getWeaponsTable().get("Fireball").isEmpty()) {
				final Weapon fireball = new Fireball(new Vec2(sp.getBody()
						.getPosition().x, sp.getBody().getPosition().y),
						direction, true);
				Map.addWeapon(fireball);
				Map.remove("Fireball");
			}
			break;
		case 2:
			if (!Map.getWeaponsTable().get("Shiboleet").isEmpty()) {
				final Weapon shiboleet = new Shiboleet(new Vec2(sp.getBody()
						.getPosition().x, sp.getBody().getPosition().y),
						direction, true);
				Map.addWeapon(shiboleet);
				Map.remove("Shiboleet");
			}
			break;
		case 3:
			if (!Map.getWeaponsTable().get("Laser").isEmpty()) {
				final Weapon laser = new Laser(new Vec2(sp.getBody()
						.getPosition().x, sp.getBody().getPosition().y),
						direction, true);
				Map.addWeapon(laser);
				Map.remove("Laser");
			}
			break;
		}
	}

	/**
	 * The adding of a player.
	 * 
	 * @param p
	 *            The player.
	 */
	public static void addPlayer(Player p) {
		Objects.requireNonNull(p);
		if (spaceshipPlayer == null) {
			spaceshipPlayer = p;
		}
	}

	/**
	 * The adding of a weapon in weaponList.
	 * 
	 * @param w
	 *            The weapon.
	 */
	public static void addWeapon(Weapon w) {
		Objects.requireNonNull(w);
		weaponsList.add(w);
	}

	/**
	 * Remove a weapon from the weapon List and the world.
	 * 
	 * @param weapon
	 */
	public static void remove(Weapon weapon) {
		world.destroyBody(weapon.getBody());
		weaponsList.remove(weapon);
	}

	/**
	 * Remove a enemy from the spaceshiplist.
	 * 
	 * @param spaceshipEnemy
	 *            The enemy.
	 */
	public static void remove(SpaceShipEnemy spaceshipEnemy) {
		world.destroyBody(spaceshipEnemy.getBody());
		spaceshipEnemyList.remove(spaceshipEnemy);
	}

	/**
	 * Remove a Bonus from the bonus List and the world.
	 * 
	 * @param Bonus
	 *            The Bonus.
	 */
	public static void remove(Bonus bonus) {
		world.destroyBody(bonus.getBody());
		bonusList.remove(bonus);
	}

	/**
	 * Remove a weapon from the weapons Table
	 * 
	 * @param weapon
	 *            The weapon.
	 */
	public static void remove(String weapon) {
		if (weaponsTable.containsKey(weapon)) {
			weaponsTable.get(weapon).remove();
		} else {
			throw new IllegalArgumentException();
		}
	}

	/**
	 * Get the world.
	 * 
	 * @return The world
	 */
	public static World getWorld() {
		return world;
	}

	/**
	 * Get the weapon List.
	 * 
	 * @return the weapon List.
	 */
	public static LinkedBlockingQueue<Weapon> getWeaponsList() {
		return Map.weaponsList;
	}

	/**
	 * Get the Player.
	 * 
	 * @return the player.
	 */
	public static Player getSpaceshipPlayer() {
		return spaceshipPlayer;
	}

	/**
	 * Get the enemylist.
	 * 
	 * @return the enemy list.
	 */
	public static LinkedBlockingQueue<SpaceShipEnemy> getSpaceshipEnemyList() {
		return spaceshipEnemyList;
	}

	/**
	 * Get the bonus list.
	 * 
	 * @return the bonus list.
	 */
	public static LinkedBlockingQueue<Bonus> getBonusList() {
		return bonusList;
	}

	/**
	 * Get the weaponActive.
	 * 
	 * @return the weapon active.
	 */
	public static int getWeaponActive() {
		return weaponActive;
	}

	/**
	 * Set the weapon Active.
	 * 
	 * @param weaponActive
	 *            the weapon active.
	 */
	public static void setWeaponActive(int weaponActive) {
		Map.weaponActive = weaponActive;
	}

	/**
	 * Get the weapons table.
	 * 
	 * @return the weapons table
	 */
	public static ConcurrentHashMap<String, Munitions> getWeaponsTable() {
		return weaponsTable;
	}

	/**
	 * Get the highscore.
	 * 
	 * @return The highscore.
	 */
	public static int getHIGHSCORE() {
		return Highscore;
	}

	/**
	 * Set the highscore.
	 * 
	 * @param hIGHSCORE
	 *            The highscore.
	 */
	public static void setHIGHSCORE(int hIGHSCORE) {
		Highscore = hIGHSCORE;
	}

	/**
	 * If we won the game.
	 * 
	 * @return <code>true</code> if we won, <code>false</code> otherwise.
	 */
	public static boolean isWIN() {
		return Win;
	}

	/**
	 * Set the victory at true.
	 * 
	 * @param wIN
	 *            The boolean win.
	 */
	public static void setWIN(boolean wIN) {
		Win = wIN;
	}

	/**
	 * If we lost the game.
	 * 
	 * @return <code>true</code> if we lost, <code>false</code> otherwise.
	 */
	public static boolean isLOSE() {
		return Lose;
	}

	/**
	 * Set the defeat at true.
	 * 
	 * @param lOSE
	 *            The boolean lose.
	 */
	public static void setLOSE(boolean lOSE) {
		Lose = lOSE;
	}

}